﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Diagnostics;
using NLog;
using DcsGauges.Shared.Packets;
using DcsGauges.Shared.Network;

namespace DcsGauges.Server.Network
{
    class ServerEndpoint
    {
        #region Inner Types

        public class GameClientConnectedEventArgs : EventArgs
        {
            public IGameChannel Channel { get; private set; }

            public GameClientConnectedEventArgs(IGameChannel gc)
            {
                this.Channel = gc;
            }
        }

        public class DcsGaugesClientConnectedEventArgs : EventArgs
        {
            public IDcsGaugesClientChannel Channel { get; private set; }

            public DcsGaugesClientConnectedEventArgs(IDcsGaugesClientChannel cc)
            {
                this.Channel = cc;
            }
        }

        #endregion


        #region Constants / Readonly

        private const int                       GAME_SEND_PORT      = 30335;
        private const int                       GAME_RECEIVE_PORT   = 30334;
        private const int                       TCP_LISTENER_PORT   = 30333;
        private const int                       BUFFER_SIZE         = 65535;
        private const string                    HOST                = "localhost";
        private UdpNetworkChannel               gameChannel         = new UdpNetworkChannel(HOST, GAME_SEND_PORT, GAME_RECEIVE_PORT);

        #endregion


        #region Field / Properties

        private List<Socket>                            clients             = new List<Socket>();
        private Dictionary<Socket, byte[]>              rawBuffers          = new Dictionary<Socket,byte[]>();
        private Dictionary<Socket, StringBuffer>        buffers             = new Dictionary<Socket, StringBuffer>();

        private static Logger                           logger              = LogManager.GetCurrentClassLogger();
        private TcpListener                             listener            = null;

        private static ServerEndpoint instance = null;
        public static ServerEndpoint Instance
        {
            get
            {
                if(instance == null)
                    instance = new ServerEndpoint();
                return instance;
            }
        }

        public bool IsRunning
        {
            get; private set; 
        }

        #endregion


        #region Events

        public event EventHandler<GameClientConnectedEventArgs>             GameClientConnectedEvent;
        public event EventHandler<DcsGaugesClientConnectedEventArgs>        DcsGaugesClientConnectedEvent;

        #endregion


        #region Constructors

        private ServerEndpoint()
        {
            this.IsRunning                                      = false;
            this.gameChannel.HandshakePacketReceivedEvent       += new EventHandler<PacketReceivedEventArgs<HandshakePacket>>(OnGameChannelHandleHandshakePacketReceivedEvent);
        }

        #endregion


        #region Operations

        public void Start()
        {
            logger.Log(LogLevel.Info, "Server endpoint started");

            this.listener                           = new TcpListener(IPAddress.Any, TCP_LISTENER_PORT);
            this.listener.Start();
            this.listener.BeginAcceptSocket(EndAcceptSocket, null);
            this.IsRunning = true;
        }


        public void Stop()
        {
            logger.Log(LogLevel.Info, "Server endpoint stopped");

            this.listener.Stop();
            this.listener = null;
            this.IsRunning = false;
        }

        
        #endregion


        #region Callbacks

        private void EndAcceptSocket(IAsyncResult result)
        {
            Socket client       = this.listener.EndAcceptSocket(result);
            clients.Add(client);
            this.rawBuffers.Add(client, new byte[BUFFER_SIZE]);
            this.buffers.Add(client, new StringBuffer());
            client.BeginReceive(this.rawBuffers[client], 0, BUFFER_SIZE, SocketFlags.None, EndReceive, client);
            this.listener.BeginAcceptSocket(EndAcceptSocket, null);
        }

        private void EndReceive(IAsyncResult result)
        {
            Socket client       = (Socket)result.AsyncState;
            int received        = client.EndReceive(result);
            this.buffers[client].Append(this.rawBuffers[client], received);

            PacketBuilder pb    = new PacketBuilder();
            Packet pack         = pb.Build(this.buffers[client]);
            if(pack != null && pack.PacketType == Packet.PacketTypes.Handshake)
            {
                HandshakePacket packet = (HandshakePacket)pack;
                if(packet.ClientType == HandshakePacket.ClientTypes.DcsA10)
                {
                    Debug.Assert(false, "Game clients are not allowed to connect by TCP anmore");
                }
                else if(packet.ClientType == HandshakePacket.ClientTypes.DcsBlackShark)
                {
                    throw new NotImplementedException();
                }
                else if(packet.ClientType == HandshakePacket.ClientTypes.DcsGauges)
                {
                    logger.Log(LogLevel.Info, "DcsGauges client accepted");
                    IDcsGaugesClientChannel channel = new DcsGaugesChannel(client, packet.ClientName);
                    this.FireGaugesClientConnectedEvent(channel);
                    this.clients.Remove(client);
                }
                else
                {
                    throw new NotImplementedException();
                }
            }
            else if(pack != null && pack.PacketType != Packet.PacketTypes.Handshake)
            {
                Debug.Assert(false);
            }
            else
            {
                //log warning half packet or invalid data received -> ignore
            }
          
        }

        #endregion


        #region Event Handlers

        void OnGameChannelHandleHandshakePacketReceivedEvent(object sender, PacketReceivedEventArgs<HandshakePacket> e)
        {
            DcsA10Channel channel = new DcsA10Channel(this.gameChannel);
            this.FireGameClientConnected(channel);
        }

        #endregion


        #region Helpers

        private void FireGameClientConnected(IGameChannel channel)
        {
            if(this.GameClientConnectedEvent != null)
            {
                this.GameClientConnectedEvent(this, new GameClientConnectedEventArgs(channel));
            }
        }

        private void FireGaugesClientConnectedEvent(IDcsGaugesClientChannel channel)
        {
            if(this.DcsGaugesClientConnectedEvent != null)
            {
                this.DcsGaugesClientConnectedEvent(this, new DcsGaugesClientConnectedEventArgs(channel));
            }
        }

        #endregion

    }
}
